//
// Created by 陈坚 on 2023/3/22.
//

#include <sys/stat.h>
#include <fcntl.h>
#include "kvstore.h"
#include <android/log.h>

//一页的大小，一般是4096
int32_t DEFAULT_MMAP_SIZE = getpagesize();

void KVStore::initializeKVStore(const char *path){
    //创建文件
    g_rootDir = path;
    //创建文件夹
    mkdir(g_rootDir.c_str(),0777);

}

KVStore *KVStore::defaultKVStore() {
    KVStore *kv = new KVStore(DEFAULT_KVSTORE_ID);
    return kv;
}

KVStore *KVStore::kvStoreWithID(const char* id) {
    KVStore *kv = new KVStore(id);
    return kv;
}

KVStore::KVStore(const char *kvstoreID) {
    //映射
    m_path = g_rootDir + "/" + kvstoreID;
    //产生映射
    loadFromFile();
}

void KVStore::loadFromFile() {
    m_fd = open(m_path.c_str(),O_RDWR | O_CREAT | O_CLOEXEC,S_IRWXU);
    //获取文件具体大小
    struct stat st = {0};
    if(fstat(m_fd,&st) != -1){
        m_size = st.st_size;//文件大小
    }
    //如果文件小大非4K整数倍，就无法映射内存和磁盘交互数据，需要纠正
    if(m_size < DEFAULT_MMAP_SIZE || (m_size % DEFAULT_MMAP_SIZE != 0)){
        //不足4k，或不是4k的整数倍
        int32_t oldSize = m_size;
        //如果m_size = 3k，则 ((3k / 4k) + 1) * 4k = 4k
        //如果m_size = 5k，则 ((5k / 4k) + 1) * 4k = 8k
        m_size = ((m_size / DEFAULT_MMAP_SIZE) + 1) * DEFAULT_MMAP_SIZE;//纠正
        //修改文件大小
        if(ftruncate(m_fd,m_size) != 0){
            m_size = st.st_size;
        }
        //如果文件大小被增加了，让增加这些大小的内容变成空
        zeroFillFile(m_fd,oldSize,m_size - oldSize);
    }
    //这时m_fd文件肯定是4k整数倍
    m_ptr = static_cast<int8_t *>(mmap(0, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd,
                                               0));
    //将文件中原有的内容缓存到map中
    //通过m_ptr虚拟地址映射物理地址直接取数据，而不是通过read，read是系统调用很耗时
    //文件头4个字节写了数据有效区长度，也就是实际长度
    memcpy(&m_actualSize,m_ptr,4);
    //m_actualSize大于0说明有数据
    if(m_actualSize > 0){
        //读取总长度后面的数据
        ProtoBuf inputBuffer(m_ptr + 4,m_actualSize);
        //清空hashmap
        m_dic.clear();
        //将文件内容解析为map
        while (!inputBuffer.isAtEnd()){//文件没有读到末尾
            //先读key
            std::string key = inputBuffer.readString();

            if(key.length() > 0){
                //key是有效的
                ProtoBuf *value = inputBuffer.readData();
                //数据有效则保存，否则删除key，因为我们是append有效数据
                if(value && value->length() > 0){
                    //hashmap保证了key的唯一性，所以从磁盘取出来重复的可以，放入m_dic也能保证其唯一，只会保存最后存储的有效数据
                    //m_dic.emplace(key,value);
                    m_dic[key] = value;
                }else{
                    m_dic.erase(key);
                }
            }
        }
    }
    //为了后续动态扩容，需要保存一份原始数据
    m_output = new ProtoBuf(m_ptr + 4 + m_actualSize,m_size - 4 - m_actualSize);
}

//将文件指定位置的数据变成空，防止有之前的脏数据
void KVStore::zeroFillFile(int fd,int32_t startPos,int32_t size){
    if(lseek(fd,startPos,SEEK_SET) < 0){
        return;
    }
    static const char zeros[4096] = {0};
    while (size >= sizeof(zeros)){
        if(write(fd,zeros,sizeof(zeros)) < 0){
            return;
        }
        size -= sizeof(zeros);
    }
    if(size > 0){
        if(write(fd,zeros,size) < 0){
            return;
        }
    }
}

void KVStore::putInt(const std::string &key, int32_t value) {
    //计算key，value的长度
    //存value需要几个字节，value的长度和内容
    int32_t size = ProtoBuf::computeValueInt32Size();
    ProtoBuf *buf = new ProtoBuf(size);
    //buf->writeRawInt(value);
    buf->writeValueInt(value);
    //写入物理内存和hashmap
    //m_dic.emplace(key,buf);
    m_dic[key] = buf;
    buf->restore();
    //写物理内存
    appendDataWithKey(key,buf);
}

void KVStore::appendDataWithKey(std::string key, ProtoBuf *value) {
    //计算待写入数据大小，key和value
    int32_t itemSize = ProtoBuf::computeItemSize(key,value);
    if(itemSize > m_output->spaceLeft()){
        //计算数据的总大小，这时需要计算m_dic的大小，因为hashmap的数据是去重的
        int32_t needSize = ProtoBuf::computeMapSize(m_dic);
        //加上前4个字节的总长度
        needSize += 4;
        //计算扩容的大小
        //计算每个item的平均长度
        int32_t avgItemSize = needSize / std::max<int32_t>(1,m_dic.size());
        //扩一半,范围：[8,总长度的一半]，需要扩容的大小
        int32_t futureUsage = avgItemSize * std::max<int32_t>(8,(m_dic.size() + 1) / 2) ;
        //needSize + futureUsage >= m_size说明需要扩容，避免里面有大量重复数据
        if(needSize + futureUsage >= m_size){
            //为了防止将来使用大小不够导致频繁重写，扩容一倍
            int32_t oldSize = m_size;
            do{
                //扩容一倍，4k扩容到8k，8k扩容到16k，...
                m_size *= 2;
            }while (needSize + futureUsage >= m_size);
            //重新设定文件大小
            ftruncate(m_fd,m_size);
            //清空文件
            zeroFillFile(m_fd,oldSize,m_size - oldSize);
            //解除映射
            munmap(m_ptr,oldSize);
            //重新映射
            m_ptr = static_cast<int8_t *>(mmap(m_ptr, m_size, PROT_WRITE | PROT_READ, MAP_SHARED,
                                               m_fd, 0));
        }
        //扩容/不扩容之后需要全量写入，这里可以解决物理内存重复数据问题，重新写入后只会存最新的一份
        //先写入有效数据大小（不包括前4个字节,前4个字节是存放键值对总大小）
        m_actualSize = needSize - 4;//总长度
        memcpy(m_ptr,&m_actualSize,4);
        //数据要遍历写入
        //删除之前的
        delete m_output;
        //创建输出 全局缓存,写入物理地址
        m_output = new ProtoBuf(m_ptr + 4,m_size - 4);
        //需要遍历hashmap全量写入
        auto iter = m_dic.begin();
        for(;iter != m_dic.end(); iter++){
            auto key = iter->first;
            auto value= iter->second;
            m_output->writeString(key);//写入磁盘
            m_output->writeData(value);//写入磁盘
        }
    }else{
        //空间足够，直接append
        //更新4个字节总长度
        m_actualSize += itemSize;
        memcpy(m_ptr,&m_actualSize,4);
        //写入key
        m_output->writeString(key);
        //写入value
        m_output->writeData(value);
    }
}

int32_t KVStore::getInt(const std::string &key, int32_t defaultValue) {
    //只需要读hashmap中的数据
    auto itr = m_dic.find(key);
    if(itr != m_dic.end()){
        //如果读到的不是end，说明有数据
        ProtoBuf *buf = itr->second;
        buf->restore();
        //int32_t storeValue = buf->readInt();
        int32_t storeValue = buf->readValueInt();
        //多次读取，将position还原为0
        buf->restore();
        return storeValue;
    }
    return defaultValue;
}

void KVStore::putString(const std::string &key,const char* value) {
    //计算key，value的长度
    //存value需要几个字节，value的长度和内容
    if(value){
        int32_t size = strlen(value);
        ProtoBuf *buf = new ProtoBuf(size);
        buf->writeValueString(value);
        //写入物理内存和hashmap
        m_dic[key] = buf;
        buf->restore();
        //写物理内存
        appendDataWithKey(key,buf);
    }else{
        removeValueFromKey(key);
    }

}

const char* KVStore::getString(const std::string &key) {
    //只需要读hashmap中的数据
    auto itr = m_dic.find(key);
    if(itr != m_dic.end()){
        //如果读到的不是end，说明有数据
        ProtoBuf *buf = itr->second;
        buf->restore();
        const char* storeValue = buf->readValueString();
        //__android_log_print(ANDROID_LOG_ERROR,"KVStore","--------storeValue：%s",storeValue);
        //多次读取，将position还原为0
        buf->restore();
        return storeValue;
    }
    return nullptr;
}

void KVStore::removeValueFromKey(const std::string &key) {
    m_dic.erase(key);
    //写大小为0的ProtoBuf物理内存
    appendDataWithKey(key,new ProtoBuf(0));
}

void KVStore::putDouble(const std::string &key, double_t value) {
    int32_t size = ProtoBuf::computeValueDouble64Size();
    ProtoBuf *buf = new ProtoBuf(size);
    buf->writeValueDouble(value);
    //写入物理内存和hashmap
    m_dic[key] = buf;
    buf->restore();
    //写物理内存
    appendDataWithKey(key,buf);
}

double_t KVStore::getDouble(const std::string &key, double_t defaultValue) {
    //只需要读hashmap中的数据
    auto itr = m_dic.find(key);
    if(itr != m_dic.end()){
        //如果读到的不是end，说明有数据
        ProtoBuf *buf = itr->second;
        buf->restore();
        double_t storeValue = buf->readValueDouble();
        //多次读取，将position还原为0
        buf->restore();
        return storeValue;
    }
    return defaultValue;
}

void KVStore::putBoolean(const std::string &key, bool value) {
    int32_t size = ProtoBuf::computeValueBooleanSize();
    ProtoBuf *buf = new ProtoBuf(size);
    buf->writeValueBoolean(value);
    //写入物理内存和hashmap
    m_dic[key] = buf;
    buf->restore();
    //写物理内存
    appendDataWithKey(key,buf);
}

bool KVStore::getBoolean(const std::string &key, bool defaultValue) {
    //只需要读hashmap中的数据
    auto itr = m_dic.find(key);
    if(itr != m_dic.end()){
        //如果读到的不是end，说明有数据
        ProtoBuf *buf = itr->second;
        buf->restore();
        bool storeValue = buf->readValueBoolean();
        //多次读取，将position还原为0
        buf->restore();
        return storeValue;
    }
    return defaultValue;
}

bool KVStore::containsKey(const std::string &key) {
    return m_dic.find(key) != m_dic.end();
}

void KVStore::putValue(const std::string &key, const void *value, const int len) {
    if(value && len > 0){
        ProtoBuf *buf = new ProtoBuf(len);
        buf->writeValue(value);
        //写入物理内存和hashmap
        m_dic[key] = buf;
        buf->restore();
        //写物理内存
        appendDataWithKey(key,buf);
    }else{
        removeValueFromKey(key);
    }
}

void* KVStore::getValue(const std::string &key) {
    //只需要读hashmap中的数据
    auto itr = m_dic.find(key);
    if(itr != m_dic.end()){
        //如果读到的不是end，说明有数据
        ProtoBuf *buf = itr->second;
        buf->restore();
        void * storeValue = buf->readValue();
        //__android_log_print(ANDROID_LOG_ERROR,"KVStore","--------storeValue：%s",storeValue);
        //多次读取，将position还原为0
        buf->restore();
        return storeValue;
    }
    return nullptr;
}

ProtoBuf *KVStore::getValueProtoBuf(const std::string &key) {
    //只需要读hashmap中的数据
    auto itr = m_dic.find(key);
    if(itr != m_dic.end()){
        //如果读到的不是end，说明有数据
        ProtoBuf *buf = itr->second;
        return buf;
    }
    return nullptr;
}

void KVStore::clear() {
    int32_t oldSize = m_size;
    m_size = DEFAULT_MMAP_SIZE;
    //重新设定文件大小
    ftruncate(m_fd,m_size);
    //清空文件
    zeroFillFile(m_fd,0,m_size);
    //解除映射
    munmap(m_ptr,oldSize);
    //重新映射
    m_ptr = static_cast<int8_t *>(mmap(m_ptr, m_size, PROT_WRITE | PROT_READ, MAP_SHARED,
                                       m_fd, 0));

    //先写入有效数据大小（不包括前4个字节,前4个字节是存放键值对总大小）
    m_actualSize = 0;//总长度
    memcpy(m_ptr,&m_actualSize,4);
    //删除之前的
    delete m_output;
    //创建输出 全局缓存,写入物理地址
    m_output = new ProtoBuf(m_ptr + 4,m_size - 4);
    //删除hashmap
    m_dic.clear();
}

